<!DOCTYPE html>
<!--
Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/math/range.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
<link rel="import" href="/tracing/value/histogram.html">

<script>
'use strict';

/**
 * @fileoverview This file contains implementations of PCScan metrics. PCScan
 * is the algorithm that eliminates use-after-free bugs by verifying that there
 * are no pointers in memory which point to explicitly freed objects before
 * actually releasing their memory.
 *
 * pa:pcscan:<process_name>:<scanner|mutator>
 * ========================
 * The overall time spent on scanning the partition alloc heap for a specific
 * process either in the scanner or mutator thread (process_name can be either 'browser_process' or 'renderer_processes').
 *
 * pa:pcscan:<process_name>:<scanner|mutator>:<phase>
 * ========================
 * Time spent on a certain PCScan phase ('clear', 'scan' or 'sweep').
 */
tr.exportTo('tr.metrics.pa', function() {
  function pcscanMetric(histograms, model) {
    function createTimeNumericForProcess(name, processName, context, desc) {
      function createNumeric(name, desc) {
        const n = new tr.v.Histogram(name,
            tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);
        n.description = desc;
        n.customizeSummaryOptions({
          avg: true,
          count: true,
          max: true,
          min: true,
          std: true,
          sum: true});
        return n;
      }
      const scheme = ['pa', 'pcscan', processName, context];
      if (name) scheme.push(name);
      return createNumeric(scheme.join(':'), desc);
    }

    function createSizeNumericForProcess(name, processName, desc) {
      function createNumeric(name, desc) {
        const n = new tr.v.Histogram(name,
            tr.b.Unit.byName.sizeInBytes_smallerIsBetter);
        n.description = desc;
        n.customizeSummaryOptions({
          avg: true,
          count: true,
          max: true,
          min: true,
          std: true,
          sum: true});
        return n;
      }
      const scheme = ['pa', 'pcscan', processName, name];
      return createNumeric(scheme.join(':'), desc);
    }

    function createPercentNumericForProcess(name, processName, desc) {
      function createNumeric(name, desc) {
        const n = new tr.v.Histogram(name,
            tr.b.Unit.byName.normalizedPercentage_smallerIsBetter);
        n.description = desc;
        n.customizeSummaryOptions({
          avg: true,
          count: true,
          max: true,
          min: true,
          std: true,
          sum: true});
        return n;
      }
      const scheme = ['pa', 'pcscan', processName, name];
      return createNumeric(scheme.join(':'), desc);
    }


    function createHistsForProcess(processName) {
      return {
        scanner_scan: createTimeNumericForProcess('scan', processName, 'scanner',
            'Time for scanning heap for quarantine pointers on concurrent threads'),
        scanner_sweep: createTimeNumericForProcess('sweep', processName, 'scanner',
            'Time for sweeping quarantine'),
        scanner_clear: createTimeNumericForProcess('clear', processName, 'scanner',
            'Time for clearing quarantine entries'),
        scanner_total: createTimeNumericForProcess('', processName, 'scanner',
            'Total time for PCScan execution on concurrent threads'),
        mutator_scan_stack: createTimeNumericForProcess('scan_stack', processName, 'mutator',
            'Time for scanning stack for quarantine pointers on mutator threads'),
        mutator_scan: createTimeNumericForProcess('scan', processName, 'mutator',
            'Time for scanning heap for quarantine pointers on mutator threads'),
        mutator_clear: createTimeNumericForProcess('clear', processName, 'mutator',
            'Time for clearing heap quarantine entries on mutator threads'),
        mutator_total: createTimeNumericForProcess('', processName, 'mutator',
            'Total time for PCScan execution on mutator threads (inside safepoints)'),
        survived_quarantine_size: createSizeNumericForProcess(
            'survived_quarantine_size', processName,
            'Size in bytes of survived quarantined objects after each *Scan cycle'),
        survived_quarantine_percent: createPercentNumericForProcess(
            'survived_quarantine_percent', processName,
            'Percent of survived quarantined objects after a *Scan cycle relative ' +
            'to the size of quarantined objects before the cycle'),
      };
    }

    function addSliceSample(hists, slice) {
      if (slice.category !== 'partition_alloc') return;
      if (!(slice instanceof tr.model.ThreadSlice)) return;

      if (slice.title === 'PCScan.Scanner.Scan') {
        hists.scanner_scan.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Scanner.Sweep') {
        hists.scanner_sweep.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Scanner.Clear') {
        hists.scanner_clear.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Scanner') {
        hists.scanner_total.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Mutator.ScanStack') {
        hists.mutator_scan_stack.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Mutator.Scan') {
        hists.mutator_scan.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Mutator.Clear') {
        hists.mutator_clear.addSample(slice.duration);
      } else if (slice.title === 'PCScan.Mutator') {
        hists.mutator_total.addSample(slice.duration);
      }
    }

    function addCounterSample(hists, counter) {
      if (counter.category !== 'partition_alloc') return;
      if (!(counter instanceof tr.model.Counter)) return;

      for (const series of counter.series) {
        for (const sample of series.samples) {
          if (counter.name === 'PCScan.SurvivedQuarantineSize') {
            hists.survived_quarantine_size.addSample(sample.value);
          } else if (counter.name === 'PCScan.SurvivedQuarantinePercent') {
            // Divide by 1000, since StatsCollector multiplies it by 1000.
            hists.survived_quarantine_percent.addSample(sample.value / 1000);
          }
        }
      }
    }

    function addHistsForProcess(processHists, processHelpers) {
      for (const helper of Object.values(processHelpers)) {
        const processName = tr.e.chrome.chrome_processes.
            canonicalizeProcessName(helper.process.name);
        if (!processHists.has(processName)) {
          processHists.set(processName, createHistsForProcess(processName));
        }
        for (const slice of helper.process.getDescendantEvents()) {
          addSliceSample(processHists.get(processName), slice);
        }
        for (const tid in helper.process.counters) {
          addCounterSample(processHists.get(processName), helper.process.counters[tid]);
        }
      }
    }

    const helper =
        model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);

    const processHists = new Map();
    addHistsForProcess(processHists, helper.browserHelpers);
    addHistsForProcess(processHists, helper.rendererHelpers);

    for (const hists of processHists.values()) {
      for (const hist of Object.values(hists)) {
        histograms.addHistogram(hist);
      }
    }
  }

  tr.metrics.MetricRegistry.register(pcscanMetric);

  return {
    pcscanMetric,
  };
});

</script>
